package com.xmall.aspect;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import com.xmall.define.dict.HasExceptionEnum;
import com.xmall.define.request.sys.OperateLogRequest;
import com.xmall.msg.service.ICommonMsgService;
import com.xmall.utils.RequestUtils;
import com.xmall.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.concurrent.Executor;

@Slf4j
@Aspect
@Component
public class OperateLogAspect {

    @Value("${spring.application.name}")
    private String serviceName;

    @Resource(name = "asyncTaskThreadPool")
    private Executor executor;

    @Resource
    private ICommonMsgService commonMsgService;

    @Pointcut("@annotation(com.xmall.aspect.OperateRecord)")
    private void pointCut() {}

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        OperateLogRequest operateLogRequest = new OperateLogRequest();
        TimeInterval timer = DateUtil.timer();

        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            OperateRecord operateRecord = method.getAnnotation(OperateRecord.class);

            operateLogRequest.setOperatePlatform(SecurityUtils.getOperatePlatform());
            operateLogRequest.setOperateId(SecurityUtils.getOperateId());
            operateLogRequest.setOperateName(SecurityUtils.getOperateName());
            operateLogRequest.setOperateAccount(SecurityUtils.getOperateAccount());
            operateLogRequest.setOperateType(operateRecord.type().getType());
            operateLogRequest.setServiceName(serviceName);

            operateLogRequest.setRequestIp(RequestUtils.getRequestIp());
            operateLogRequest.setRequestPath(RequestUtils.getRequestPath());
            operateLogRequest.setRequestJson(RequestUtils.getRequestJson(joinPoint));
            operateLogRequest.setRequestTime(new Date());
        }catch (Exception e) {
            log.error("OperateLogAspect error before execute: {}", e.getMessage());
        }

        Object result;
        Object[] args = joinPoint.getArgs();
        try {
            result = joinPoint.proceed(args);

            try {
                operateLogRequest.setRunMilliseconds(timer.interval());
                operateLogRequest.setHasException(HasExceptionEnum.NO.getValue());
                operateLogRequest.setResponseJson(JSONUtil.toJsonStr(result));

                executor.execute(() -> commonMsgService.sendOperateLog(operateLogRequest));
            }catch (Exception e) {
                log.error("OperateLogAspect error after execute: {}", e.getMessage());
            }

        } catch (Throwable throwable) {
            try {
                operateLogRequest.setRunMilliseconds(timer.interval());
                operateLogRequest.setHasException(HasExceptionEnum.YES.getValue());
                operateLogRequest.setException(throwable.getMessage());

                executor.execute(() -> commonMsgService.sendOperateLog(operateLogRequest));
            }catch (Exception e) {
                log.error("OperateLogAspect error after execute failed: {}", e.getMessage());
            }

            throw throwable;
        }

        return result;
    }
}
